import { Link } from '@brillout/docpress'
import { EventBasedRecommendation } from '../../components'

> **What is this about?**
>
> This page explains how to most efficiently use Telefunc (and RPC in general) to significantly increase development speed.
>
> See <Link href="/RPC" /> if you aren't familiar with RPC.

With REST and GraphQL, API endpoints are:

- Generic (agnostic to your frontend needs)
- Backend-owned (defined and implemented by the backend team)

With Telefunc, it's usually the opposite — telefunctions are:

- Tailored (specific to your frontend needs)
- Frontend-owned (defined and implemented by the frontend team)

This inversion is at the cornerstone of using Telefunc proficiently.

You may be tempted to create generic telefunctions but we recommend against it. Instead, we recommend implementing what we call *event-based telefunctions*.

```ts
// database/todo.telefunc.ts

// ❌ Generic telefunction: one telefunction re-used for multiple use cases
export async function updateTask(id: number, modifications: Partial<TodoItem>) {
  // ...
}
```

```ts
// components/TodoList.telefunc.ts

// ✅ Event-based telefunctions: one telefunction per use case

export async function onTodoTextUpdate(id: number, text: string) {
  // ...
}
export async function onTodoCompleted(id: number) {
  // ...
}
```

In the example below, we explain why event-based telefunctions lead to increased:
 - Development speed (while we explain how to keep telefunctions DRY)
 - Security
 - Performance


## Example

Imagine an existing to-do list app, and the product manager requests a new feature: add a new button `Mark all tasks as completed`.

With a RESTful API, the app would typically do this:

```shell
HTTP            URL                                           PAYLOAD
=========       =========================================     =====================
# Make a request to fetch all non-completed tasks
GET             https://api.todo.com/task?completed=false     ∅
# Make a request per task to update it
POST            https://api.todo.com/task/42                  { "completed": true }
POST            https://api.todo.com/task/1337                { "completed": true }
POST            https://api.todo.com/task/7                   { "completed": true }
```

{/*
Couldn't find a reference to link to:
- Wikipedia doesn't have an article about the N+1 problem
- https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping
  - But it refers to ORMs and not REST
- https://www.prisma.io/docs/orm/prisma-client/queries/query-optimization-performance
  - Convoluted
- Drizze doesn't have any docs about the N+1 problem
- Anyways, I feel like uses nowadays don't care much about these theoretical concepts anymore
*/}
This is inefficient as it makes a lot of HTTP requests (the infamous `N+1` problem).

With Telefunc, you can do this instead:

```ts
// components/TodoList.telefunc.ts
// Environment: server

import { Tasks } from '../database/Tasks'

export async function onMarkAllAsCompleted() {
  // With an ORM:
  await Tasks.update({ completed: true }).where({ completed: false })
  /* Or with SQL:
  await sql('UPDATE tasks SET completed = true WHERE completed = false')
  */
}
```

The telefunction `onMarkAllAsCompleted()` is tailored: it's created specifically to serve the needs of the `<TodoList>` component. It's simpler and a lot more efficient.


#### Convention

We recommend naming telefunctions `onSomeEvent()` (see <Link href="#naming-convention"/>), because telefunction calls are always triggered by some kind of event — typically a user action, such as the user clicking on a button.

```shell
# Also: we recommend co-locating .telefunc.js files
components/TodoList.telefunc.ts # telefunctions for <TodoList>
components/TodoList.tsx # <TodoList> implementation
```

```tsx
// components/TodoList.tsx
// Environment: client

import { onMarkAllAsCompleted } from './TodoList.telefunc.ts'

function TodoList() {
  return (
    <>
      {/* ... */}
      <button onClick={onMarkAllAsCompleted}>Mark all as completed</button>
    </>
  )
}
```

This naming convention ensures telefunctions are tightly coupled to UI components.

> With Telefunc, you think in terms of what the frontend needs (instead of thinking of the backend as a generic data provider). From that perspective, it makes more sense to co-locate telefunctions next to UI components (instead of next to where data comes from).

#### Too restrictive convention?

To keep telefunctions [DRY](https://softwareengineering.stackexchange.com/questions/400183/what-should-i-consider-when-the-dry-and-kiss-principles-are-incompatible) you may be tempted to define a single telefunction that is re-used by many UI components. For example:

```ts
// database/actions/tasks.telefunc.ts
// Environment: server

import { Task } from '../models/Task'
import { getContext } from 'telefunc'

// One telefunction used by multiple UI components
export async function updateTask(id: number, mods: Partial<typeof Task>) {
  const { user } = getContext()
  const task = await Task.update(mods).where({ id, author: user.id })
  // Returns the updated value task.modifiedAt
  return task
}
```

But this generic telefunction has two issues:
1. It isn't safe.
   > As explained at <Link href="/RPC" />, telefunctions are public. This means any user can call `updateTask({ author: Math.floor(Math.random()*100000) })` which is a big security issue.
2. It isn't efficient.
   > Because `updateTask()` is generic, it must `return task` in case a component requires `task.modifiedAt` — but if some components don't need it, this results in wasted network bandwidth.

This shows how easy it is to introduce security issues and inefficiencies with generic telefunctions.

Generic telefunctions typically:
- Make reasoning about <Link href="/RPC#security">RPC security</Link> harder, leading to subtle bugs and security issues.
- Decrease <Link href="/RPC#performance">RPC performance</Link>.

We recommend the following instead:

```ts
// database/actions/task.ts

import { Task } from '../models/Task'
import { getContext } from 'telefunc'

// This isn't a telefunction: it's a normal server-side function
export async function updateTask(id: number, mods: Partial<typeof Task>) {
  const { user } = getContext() // Can also be used in normal functions
  const task = await Task.update(mods).where({ id, author: user.id })
  // Returns the updated value task.modifiedAt
  return task
}
```

```ts
// components/TodoList.telefunc.ts

import { updateTask } from '../database/actions/task'

export const onTodoTextUpdate = async (id: number, text: string) => {
  const task = await updateTask(id, { text })
  // Returns task.modifiedAt
  return task
}
export const onTodoCompleted = async (id: number) => {
  await updateTask(id, { completed: false })
  // Doesn't return task
}
```

It's slightly less DRY but, in exchange, you get a much clearer structure around security and performance.

When a telefunction is tightly coupled with a component:
 - The telefunction's return value can be minimal (exactly and only what is needed by the component), which leads to increased performance.
 - The telefunction's arguments can be minimal (exactly and only what is needed by the component), which leads to increased security.
 - The telefunction can allow only what is strictly required by the component.
   > A cornerstone of security is to grant only the permissions that are strictly required.

That's why we recommend event-based telefunctions, along with the naming convention to ensure telefunctions are tightly coupled to components.

> If there are two UI components that could use the exact same telefunction — wouldn't it be nice to create a single telefunction instead of duplicating the same telefunction?
> - It's a rare situation (UI components usually have slightly different requirements).
> - Consider creating a new shared UI component used by these two components.
> - Using the deduplication approach shown above, only one line of duplicated code remains:
>   ```ts
>   // TodoItem.telefunc.ts
>   // Defined once for <TodoItem>
>   export const onTodoTextUpdate = (id: number, text: string) => updateTask(id, { text })
>   ```
>   ```ts
>   // TodoList.telefunc.ts
>   // Defined again for <TodoList> — the code duplication is only one line of code
>   export const onTodoTextUpdate = (id: number, text: string) => updateTask(id, { text })
>   ```


## Naming convention

As explained in <Link href="#example">the example above</Link>, for a clear structure and proficient Telefunc usage, we recommend the following convention.

Name telefunctions `onSomeEvent()`:

```bash
    TELEFUNCTIONS
    =============
❌  updateTodo()
✅  onTodoTextUpdate()
✅  onTodoComplete()

❌  loadData()
✅  onLoad()
✅  onPagination()
✅  onInfiniteScroll()
```

Co-locate `.telefunc.js` files next to UI component files:

```shell
    FILES
    =====
    components/TodoItem.tsx
✅  components/TodoItem.telefunc.ts
❌  database/todo.telefunc.ts

    components/User.vue
✅  components/User.telefunc.js
❌  database/user/getLoggedInUser.telefunc.js
```

This convention is optional and you can opt-out.

### Opt out

Telefunc shows a warning if you don't follow the naming convention — you can opt-out of the convention and remove the warning by setting <Link text={<code>config.disableNamingConvention</code>} href="/disableNamingConvention" />.

<EventBasedRecommendation samePage />


## See also

- <Link href="/disableNamingConvention" />
- <Link href="/multiple-clients" />
